package io.cici.cc.mybatis.lite.executor;

import io.cici.cc.mybatis.lite.executor.resultset.ResultContextHandler;
import io.cici.cc.mybatis.lite.mapping.MappedStatement;
import io.cici.cc.mybatis.lite.mapping.Sql;
import io.cici.cc.mybatis.lite.session.Configuration;
import io.cici.cc.mybatis.lite.transaction.Transaction;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

@Slf4j
@Setter
@Getter
public abstract class BaseExecutor implements Executor {

    protected Transaction transaction;
    protected Executor wrapper;

    protected Configuration configuration;

    private boolean closed;

    protected BaseExecutor(Configuration configuration, Transaction transaction) {
        this.transaction = transaction;

        this.closed = false;
        this.configuration = configuration;
        this.wrapper = this;
    }

    @Override
    public Transaction getTransaction() {
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        return transaction;
    }

    @Override
    public void close(boolean forceRollback) {
        try {
            try {
                rollback(forceRollback);
            } finally {
                if (transaction != null) {
                    transaction.close();
                }
            }
        } catch (SQLException e) {
            // Ignore. There's nothing that can be done at this point.
            log.warn("Unexpected exception on closing transaction.  Cause: " + e);
        } finally {
            transaction = null;
            closed = true;
        }
    }

    @Override
    public boolean isClosed() {
        return closed;
    }

    @Override
    public int update(MappedStatement mappedStatement, Object parameter) throws SQLException {
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        return doUpdate(mappedStatement, parameter);
    }

    @Override
    public List<BatchResult> flushStatements() throws SQLException {
        return flushStatements(false);
    }

    public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        return doFlushStatements(isRollBack);
    }

    @Override
    public <E> List<E> query(MappedStatement mappedStatement, Object parameter,
                             ResultContextHandler resultContextHandler) throws SQLException {
        Sql sql = mappedStatement.getSql(parameter);

        return query(mappedStatement, parameter, resultContextHandler, sql);
    }

    @SuppressWarnings("unchecked")
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, ResultContextHandler resultContextHandler, Sql sql)
            throws SQLException {
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        List<E> list = queryFromDatabase(ms, parameter, resultContextHandler, sql);
        return list;
    }

    @Override
    public void commit(boolean required) throws SQLException {
        if (closed) {
            throw new ExecutorException("Cannot commit, transaction is already closed");
        }

        flushStatements();
        if (required) {
            transaction.commit();
        }
    }

    @Override
    public void rollback(boolean required) throws SQLException {
        if (!closed) {
            try {

                flushStatements(true);
            } finally {
                if (required) {
                    transaction.rollback();
                }
            }
        }
    }

    protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;

    protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;

    protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter,
                                           ResultContextHandler resultContextHandler, Sql sql) throws SQLException;

    protected void closeStatement(Statement statement) {
        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                // ignore
            }
        }
    }

    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter,
                                          ResultContextHandler resultContextHandler, Sql sql) throws SQLException {
        List<E> list = doQuery(ms, parameter, resultContextHandler, sql);

        return list;
    }

    protected Connection getConnection() throws SQLException {
        Connection connection = transaction.getConnection();
        return connection;
    }

    @Override
    public void setExecutorWrapper(Executor wrapper) {
        this.wrapper = wrapper;
    }

}
